package speed2010.merger;

import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;

//import matcher.Matching;

import org.semanticweb.owl.apibinding.OWLManager;
import org.semanticweb.owl.io.RDFXMLOntologyFormat;
import org.semanticweb.owl.model.AddAxiom;
import org.semanticweb.owl.model.OWLAxiom;
import org.semanticweb.owl.model.OWLClass;
import org.semanticweb.owl.model.OWLDataFactory;
import org.semanticweb.owl.model.OWLDescription;
import org.semanticweb.owl.model.OWLOntology;
import org.semanticweb.owl.model.OWLOntologyManager;
import org.semanticweb.owl.model.RemoveAxiom;

import speed2010.matcher.semanticmatching.basics.Alignment;

import speed2010.matcher.semanticmatching.SemanticMatcher;
import speed2010.util.SpeedClusterOntology;
import uk.ac.manchester.cs.owl.OWLSubClassAxiomImpl;

public class SemMerging {

	private String domainOntology;
	private String O1;
	private String O2;
	private String result;
	private ArrayList<Alignment> alignments;

	/** @author ncc */
	private SpeedClusterOntology clusterOntology; // Atributo utilizado para contabilizar a frequencia de cada classe da ontologia

	// adicionais
	private ArrayList<String> classesAdicionadas;

	public SemMerging(String pathDomain, String pathO1, String pathO2, String pathResult, SpeedClusterOntology clusterOntology) {
		this.domainOntology = pathDomain;
		this.O1 = pathO1;
		this.O2 = pathO2;
		this.result = pathResult;
		this.clusterOntology = clusterOntology;
		this.classesAdicionadas = new ArrayList<String>();
	}

	public String getO1() {
		return O1;
	}

	public void setO1(String o1) {
		O1 = o1;
	}

	public String getO2() {
		return O2;
	}

	public void setO2(String o2) {
		O2 = o2;
	}

	public String getResult() {
		return result;
	}

	public void setResult(String result) {
		this.result = result;
	}

	public String getDomainOntology() {
		return domainOntology;
	}

	public void setDomainOntology(String domainOntology) {
		this.domainOntology = domainOntology;
	}

	public ArrayList<Alignment> getAlignments() {
		return alignments;
	}

	public void setAlignments(ArrayList<Alignment> alignments) {
		this.alignments = alignments;
	}

	public ArrayList<String> getClassesAdicionadas() {
		return classesAdicionadas;
	}

	public String father(Set<OWLAxiom> axioms, String classX) {


		String retorno = "";
		//procurar os domains dos axiomas do tipo OWLSubClassAxiomImpl onde o range é a classX
		//sair voltando, ou seja, pegar o pai do pai, do pai, até n encontrar mais nenhum OWLSubClassAxiomImpl, ai pára
		// e retorna esse ArrayList com todos os pais.

		for (OWLAxiom axiom : axioms) {
			if(axiom instanceof OWLSubClassAxiomImpl) {
				OWLDescription superClass = ((OWLSubClassAxiomImpl) axiom).getSuperClass();
				OWLDescription subClass = ((OWLSubClassAxiomImpl) axiom).getSubClass();

				if(subClass.toString().toLowerCase().equals(classX.toLowerCase())) {
					retorno = superClass.toString();
				}
			}
		}

		return retorno;
	}

	public ArrayList<String> listOfFathers(Set<OWLAxiom> axioms, String classX) {
		ArrayList<String> retorno = new ArrayList<String>();
		String father = father(axioms, classX);

		while(!father.equals("")) {
			retorno.add(father);
			father = father(axioms, father);
		}

		return retorno;
	}

	public void generateAlignments() throws Exception {
		SemanticMatcher sm = new SemanticMatcher(O1, O2, domainOntology);
		alignments = sm.executeSemanticMatching();

		//		Iterator<Alignment> it = align.iterator();
		//		while(it.hasNext()) {
		//			Alignment a = it.next();
		//			System.out.print("subject: " + a.getSubject() + " | ");
		//			System.out.print("predicate: " + a.getPredicate()+ " | ");
		//			System.out.print("object: " + a.getObject()+ " | ");
		//			System.out.println("weight: " + a.getWeight());
		//		}
	}

	public void start() throws Exception {

		//creating paths
		
		System.out.println("file:///" + this.domainOntology);
		System.out.println("file:///" + this.O1);
		URI domainURI = URI.create("file:///" + this.domainOntology);
		URI O1URI = URI.create("file:///" + this.O1);
		URI O2URI = URI.create("file:///" + this.O2);
		URI resultPath = URI.create("file:///" + this.result);

		//initializing the ontologies
		OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
		OWLOntology ontologyO1 = manager.loadOntologyFromPhysicalURI(O1URI);
		OWLOntology ontologyO2 = manager.loadOntologyFromPhysicalURI(O2URI);
		OWLOntology ontologyDomain = manager.loadOntologyFromPhysicalURI(domainURI);

		Iterator<OWLClass> classesO1 = ontologyO1.getReferencedClasses().iterator();
		ArrayList<String> classesO1Names = new ArrayList<String>();

		//Preenchendo um ArrayList com as classes da Ontologia 1
		while (classesO1.hasNext()) {
			String s = classesO1.next().getURI().getRawFragment();
			classesO1Names.add(s);
		}


		OWLDataFactory factory = manager.getOWLDataFactory();

		generateAlignments();
		Iterator<Alignment> it = alignments.iterator();

		//nesse loop cada correspondencia eh analisada
		while(it.hasNext()) {
			Alignment c = it.next();
			String tipo = c.getPredicate().getRelationName();
			String classeO1 = c.getSubject().getRealName();
			String classeO2 = c.getObject().getRealName();

			if(tipo.equals("isSubConceptOf")) {
				boolean superClassO2Exists = false;

				// loop pra verificar se classO2 existe na ontologia O1
				Iterator<String> itNames = classesO1Names.iterator();
				while (itNames.hasNext() && !superClassO2Exists) {
					String s = itNames.next();
					if(s.toLowerCase().equals(classeO2.toLowerCase())) {
						superClassO2Exists = true;
					}
				}

				// se classO2 n existir em O1 ela precisa ser adicionada, contudo n pode ser adiciona em qualquer lugar
				// tem q ser adicionada como subclasse da classe que mais se aproxima do seu pai na ontologia de dominio
				if(!superClassO2Exists) {

					//tenho que adicionar a classeO2 como subclasse da classe que eh mais proxima do seu pai original (o seu pai na ontologia de dominio)
					ArrayList<String> listOfFathersO2 = listOfFathers(ontologyDomain.getAxioms(), classeO2);
					ArrayList<String> listOfFathersO1 = listOfFathers(ontologyO1.getAxioms(), classeO1);
					Iterator<String> itFathersO2 = listOfFathersO2.iterator();
					Iterator<String> itFathersO1 = listOfFathersO1.iterator();

					boolean inserted = false;

					while(itFathersO2.hasNext() && !inserted) {
						String fatherO2 = itFathersO2.next();
						while(itFathersO1.hasNext() && !inserted) {
							String fatherO1 = itFathersO1.next();
							if(fatherO2.toLowerCase().equals(fatherO1.toLowerCase())) {
								OWLClass c1 = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + fatherO1));
								OWLClass c2 = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO2));
								OWLAxiom subAxiom = factory.getOWLSubClassAxiom(c2, c1);
								AddAxiom add = new AddAxiom(ontologyO1, subAxiom);
								manager.applyChange(add);
								inserted = true;
								classesAdicionadas.add(classeO2);
							}
						}
					}

					// se eu n encontrar nenhum pai proximo aos pais originais de classO2, entao n resta escolha
					// se n adicionar a classe a thing (raiz)
					if(!inserted) {
						OWLClass cls = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO2));
						OWLAxiom axiom = factory.getOWLDeclarationAxiom(cls);
						manager.addAxiom(ontologyO1, axiom);
						classesAdicionadas.add(classeO2);
					}

				}

				// agora classO2 jah existe, entao eh so adicionar ela como pai de classO1
				// (pode ser q classO2 jah seja pai de classO1, mas ai eu removo de qualquer jeito o pai de classO1 pra evitar verificacoes)

				// removendo o antigo relacionamento entre classO1 e seu antigo pai
				OWLClass superClassO1 = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + father(ontologyO1.getAxioms(), classeO1)));
				OWLClass classO1 = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO1));
				OWLAxiom axiom = factory.getOWLSubClassAxiom(classO1, superClassO1);
				RemoveAxiom rem = new RemoveAxiom(ontologyO1, axiom);
				manager.applyChange(rem);

				// por fim adiciono classO2 como pai de classO1
				OWLClass superClass = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO2));
				OWLClass subClass = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO1));
				OWLAxiom subAxiom = factory.getOWLSubClassAxiom(subClass, superClass);
				AddAxiom add = new AddAxiom(ontologyO1, subAxiom);
				manager.applyChange(add);


			} else if (tipo.equals("isSuperConceptOf")) {

				boolean temSubClasseO2 = false;

				// loop pra verificar se classO2 existe na ontologia O1
				Iterator<String> itNames = classesO1Names.iterator();
				while (itNames.hasNext() && !temSubClasseO2) {
					String s = itNames.next();
					if(s.toLowerCase().equals(classeO2.toLowerCase())) {
						temSubClasseO2 = true;
					}
				}

				// se a classe existir verificar se ela estah no lugar certo
				if(temSubClasseO2) {
					Set<OWLAxiom> axioms = ontologyO1.getAxioms();

					for(OWLAxiom axiom : axioms)  {

						if(axiom instanceof OWLSubClassAxiomImpl) {
							OWLDescription d1 = ((OWLSubClassAxiomImpl) axiom).getSuperClass();
							OWLDescription d2 = ((OWLSubClassAxiomImpl) axiom).getSubClass();

							// classeO2 eh filha
							if(d2.toString().toLowerCase().equals(classeO2.toLowerCase())) {

								// classeO1 eh pai
								if(d1.toString().toLowerCase().equals(classeO1.toLowerCase())) {

									// classeO1 n eh pai
								} else {

									// removendo o antigo relacionamento entre classeO2 e seu antigo pai
									OWLClass antigoPai = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + d1.toString()));
									OWLClass cO2 = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO2));
									OWLAxiom axioma = factory.getOWLSubClassAxiom(cO2, antigoPai);
									RemoveAxiom rem = new RemoveAxiom(ontologyO1, axioma);
									manager.applyChange(rem);

									// por fim adiciono classO1 como pai de classO2
									OWLClass superClass = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO1));
									OWLClass subClass = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO2));
									OWLAxiom subAxiom = factory.getOWLSubClassAxiom(subClass, superClass);
									AddAxiom add = new AddAxiom(ontologyO1, subAxiom);
									manager.applyChange(add);

								}

							}
						}
					}

					// se a classe n existir criar ela e colocar como subclasse de classeO1	
				} else {
					OWLClass c1 = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO1));
					OWLClass c2 = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO2));
					OWLAxiom subAxiom = factory.getOWLSubClassAxiom(c2, c1);
					AddAxiom add = new AddAxiom(ontologyO1, subAxiom);
					manager.applyChange(add);
					classesAdicionadas.add(classeO2);
				}

			}

		}

		// preencher o arrayList com as classes de O1
		Iterator<OWLClass> O1Classes = ontologyO1.getReferencedClasses().iterator();
		ArrayList<String> O1ClassNames = new ArrayList<String>();

		while (O1Classes.hasNext()) {
			String s = O1Classes.next().getURI().getRawFragment();
			O1ClassNames.add(s);
		}

		// preencher o arrayList com as classes de O2
		Iterator<OWLClass> O2Classes = ontologyO2.getReferencedClasses().iterator();
		ArrayList<String> O2ClassNames = new ArrayList<String>();

		while (O2Classes.hasNext()) {
			String s = O2Classes.next().getURI().getRawFragment();
			O2ClassNames.add(s);
		}

		// arrayList de classes de O2 n adicionadas
		ArrayList<String> classesSemPai = new ArrayList<String>();

		// adicionar as classes que n foram adicionadas ainda no arrayList
		Iterator<String> iterat = O2ClassNames.iterator();
		while(iterat.hasNext()) {
			String classeO2 = iterat.next();
			if(!O1ClassNames.contains(classeO2)) {
				OWLClass cls = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeO2));
				OWLAxiom axiom = factory.getOWLDeclarationAxiom(cls);
				manager.addAxiom(ontologyO1, axiom);
				classesSemPai.add(classeO2);
				O1ClassNames.add(classeO2);
			}
		}

		Iterator<String> clsNaoAdd = classesSemPai.iterator();
		while(clsNaoAdd.hasNext()) {
			String classeAtual = clsNaoAdd.next();

			// a classeAtual tem q ser adicionada como subclasse da classe que mais se aproxima do seu pai na ontologia de dominio
			ArrayList<String> listOfFathersO2 = listOfFathers(ontologyDomain.getAxioms(), classeAtual);
			Iterator<String> itFathersO2 = listOfFathersO2.iterator();
			Iterator<String> itClassesO1 = O1ClassNames.iterator();

			boolean inserted = false;

			while(itFathersO2.hasNext() && !inserted) {
				String fatherO2 = itFathersO2.next();

				while(itClassesO1.hasNext() && !inserted) {
					String classO1 = itClassesO1.next();

					if(fatherO2.toLowerCase().equals(classO1.toLowerCase())) {
						OWLClass c1 = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classO1));
						OWLClass c2 = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeAtual));
						OWLAxiom subAxiom = factory.getOWLSubClassAxiom(c2, c1);
						AddAxiom add = new AddAxiom(ontologyO1, subAxiom);
						manager.applyChange(add);
						inserted = true;
						classesAdicionadas.add(classeAtual);
					}
				}
			}

			// se eu n encontrar nenhum pai proximo aos pais originais de classO2, entao n resta escolha
			// se n adicionar a classe a thing (raiz)
			if(!inserted) {
				OWLClass cls = factory.getOWLClass(URI.create(ontologyO1.getURI() + "#" + classeAtual));
				OWLAxiom axiom = factory.getOWLDeclarationAxiom(cls);
				manager.addAxiom(ontologyO1, axiom);
			}
		}

		manager.saveOntology(ontologyO1, new RDFXMLOntologyFormat(),resultPath);
		
		/** Modificado por @author ncc em 11/10/2012
		 * Depois de realizar o merge (com sucesso) deve-se atualizar a frequencia dos conceitos da nova
		 * ontologia do cluster
		 */
		
		Iterator<OWLClass> itClassesO2 = ontologyO2.getReferencedClasses().iterator();
		ArrayList<String> nameClasses = new ArrayList<String>();
		
		while (itClassesO2.hasNext()){
			String name = itClassesO2.next().getURI().getRawFragment();
			nameClasses.add(name);
		}
		
		this.clusterOntology.ontology.updateConceptFrequency(nameClasses);
		
	}

}
